/* SPDX-License-Identifier: GPL-2.0 */
/*
 * relocate_kernel.S - put the kernel image in place to boot
 */

#include <linux/linkage.h>
#include <asm/assembler.h>
#include <asm/asm-offsets.h>
#include <asm/kexec.h>

#ifdef CONFIG_HISI_WATCHDOG
#ifdef CONFIG_PHYS_ADDR_T_64BIT
#define PHY_ADDR_TYPE_SIZE 8
#ifdef CONFIG_CPU_BIG_ENDIAN
#define LOAD_OFFSET 4
#else
#define LOAD_OFFSET 0
#endif
#else
#define LOAD_OFFSET 0
#define PHY_ADDR_TYPE_SIZE 4
#endif
#define OFFSET_DROL_OP_REG	0
#define OFFSET_DROL_OP_ADDR	(OFFSET_DROL_OP_REG + PHY_ADDR_TYPE_SIZE)
#define OFFSET_DROL_OP_RESUSE	(OFFSET_DROL_OP_ADDR + 4)
#define OFFSET_DROL_OP_OP	(OFFSET_DROL_OP_RESUSE + 4)
#define OFFSET_DROL_OP_CLEAR	(OFFSET_DROL_OP_OP + 4)
#define OFFSET_DROL_OP_SET	(OFFSET_DROL_OP_CLEAR + 4)
#define OFFSET_DROL_OP_XOR	(OFFSET_DROL_OP_SET + 4)
#define OFFSET_DROL_OP_DELAY	(OFFSET_DROL_OP_XOR + 4)
#define DROL_OP_STRUCT_SIZE	(OFFSET_DROL_OP_DELAY + 4)
#define DELAY_UNIT              1000
#endif

RTOS_CFI_TAG_ALIGN(3)	/* not needed for this code, but keeps fncpy() happy */

RTOS_ENTRY_NOTAG(relocate_new_kernel)

	adr	r7, relocate_new_kernel_end
	ldr	r0, [r7, #KEXEC_INDIR_PAGE]
	ldr	r1, [r7, #KEXEC_START_ADDR]

#ifdef CONFIG_HISI_WATCHDOG
	mov r13, #0
#endif
	/*
	 * If there is no indirection page (we are doing crashdumps)
	 * skip any relocation.
	 */
	cmp	r0, #0
	beq	2f

0:	/* top, read another word for the indirection page */
	ldr	r3, [r0],#4

	/* Is it a destination page. Put destination address to r4 */
	tst	r3,#1
	beq	1f
	bic	r4,r3,#1
	b	0b
1:
	/* Is it an indirection page */
	tst	r3,#2
	beq	1f
	bic	r0,r3,#2
	b	0b
1:

	/* are we done ? */
	tst	r3,#4
	beq	1f
	b	2f

1:
	/* is it source ? */
	tst	r3,#8
	beq	0b
	bic r3,r3,#8
	mov r6,#1024
9:
	ldr r5,[r3],#4
	str r5,[r4],#4
	subs r6,r6,#1
	bne 9b
#ifdef CONFIG_HISI_WATCHDOG
	cmp r13, #0x400 /* feed dog for every 4MB */
	blt loop_inc
	mov r13, #0
	adr r8, wacthdog_kexec_op
	adr r9, wacthdog_kexec_op_size
	ldr r9, [r9]
	mov r10, #0x0
loop_start:
	cmp r10, r9
	bge out
	ldr r11, [r8, #OFFSET_DROL_OP_DELAY]
	mov r12, #DELAY_UNIT /* reg delay check */
	mul r11, r11, r12
	mov r12, #0 /* reg delay check */
delay_loop:
	add r12, r12, #1
	cmp r12, r11
	blt delay_loop
	mov r12, #0 /* val = 0 */
	ldr r11, [r8, #OFFSET_DROL_OP_OP] /* get op.op */
	tst r11, #1
	beq write_reg
	mov r12, #OFFSET_DROL_OP_REG
	add r12, r12, #LOAD_OFFSET
	ldr r12, [r8, r12] /* get value of op.reg means phy addr */
	ldr r12, [r12] /* get value that store in addr:op.reg */
#ifdef CONFIG_CPU_BIG_ENDIAN
	rev r12, r12
#endif
write_reg:
	tst r11, #2
	beq next
	ldr r11, [r8, #OFFSET_DROL_OP_CLEAR] /* get op.clear */
	bic r12, r12, r11
	ldr r11, [r8, #OFFSET_DROL_OP_SET] /* get op.set */
	orr r12, r12, r11
	ldr r11, [r8, #OFFSET_DROL_OP_XOR] /* get op.xor */
	eor r12, r12, r11
	mov r11, #OFFSET_DROL_OP_REG
	add r11, r11, #LOAD_OFFSET
	ldr r11, [r8, r11] /* get op.reg */
#ifdef CONFIG_CPU_BIG_ENDIAN
	rev r12, r12
#endif
	str r12, [r11]
next:
	add r10, r10, #1 /* try next loop */
	add r8, r8, #DROL_OP_STRUCT_SIZE /* get next drol_op struct */
	b loop_start
loop_inc:
	add r13, r13, #1
out:
#endif
	b 0b

2:
	/* Jump to relocated kernel */
	mov	lr, r1
	mov	r0, #0
	ldr	r1, [r7, #KEXEC_MACH_TYPE]
	ldr	r2, [r7, #KEXEC_R2]
 ARM(	ret	lr	)
 THUMB(	bx	lr	)

ENDPROC(relocate_new_kernel)
#ifdef CONFIG_HISI_WATCHDOG
	.globl wacthdog_kexec_op_size
wacthdog_kexec_op_size:
	.long 0x0
	.globl wacthdog_kexec_op
wacthdog_kexec_op:
	.space 0x400
#endif

	.align	3
relocate_new_kernel_end:

	.globl relocate_new_kernel_size
relocate_new_kernel_size:
	.long relocate_new_kernel_end - relocate_new_kernel


